package sentinel

import (
	"config"
	"devserv"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"log"
	"rhelper"
	"time"
)

const (
	//try how many times for get master addr
	maxCount = 50

	//sleep how many time for each times get master addr failed
	sleepTime = 15
)

var (
	rm *RecoveryManager
)

type RecoveryManager struct {
	isDoingRecovery bool
	psc             *redis.PubSubConn
}

func (rm *RecoveryManager) subScribeMsg() {
	if !rm.isDoingRecovery {
		return
	}

	if rm.psc != nil {
		rm.psc.Unsubscribe()
	}

	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	psc := &redis.PubSubConn{Conn: rconn}
	psc.Subscribe(config.ServerId)
	rm.psc = psc

	for {
		switch n := rm.psc.Receive().(type) {
		case redis.Message:
			fmt.Printf("Message: %s %s\n", n.Channel, n.Data)
			go devserv.OnUserRequest(n.Data)
		case redis.Subscription:
			log.Printf("%s %s %d\n", n.Kind, n.Channel, n.Count)
			if n.Count == 0 {
				rm.psc = nil
				return
			}

			rm.isDoingRecovery = false
		case error:
			log.Printf("subscribe error: %v\n", n)
			rm.psc = nil
			doRecovery()
			return
		default:
			log.Printf("unknow subscribe type message: %v\n", n)

		}
	}
}

func (rm *RecoveryManager) tryToGetMasterAddr(sentinel *SentinelClient) (addr string, err error) {
	count := 0

	for {
		if !rm.isDoingRecovery {
			return
		}

		masterAddr, err := sentinel.QueryConfForMaster(config.MasterName)
		if err != nil {
			log.Println("query master error: ", err)
			panic(err)
		}

		conn, err := redis.Dial(sentinel.net, masterAddr)
		if err != nil {
			log.Println("dial master err, masterAddr: ", masterAddr)

			if count > maxCount {
				break
			}

			count++
			time.Sleep(time.Second * sleepTime)

			continue
		}

		if config.RedisPasswd != "" {
			_, err := conn.Do("AUTH", config.RedisPasswd)
			if err != nil {
				conn.Close()
				return "", err
			}
		}

		if TestRole(conn, "master") {
			return masterAddr, nil
		} else {
			log.Println("tryToGetMasterAddr, sentinel get master address, but role not match")
		}

		if count > maxCount {
			break
		}

		time.Sleep(time.Second * sleepTime)
		count++
	}

	return "", fmt.Errorf("time out, get master addr not match")
}

func (rm *RecoveryManager) doRecovery() {
	log.Println("SentinelEnable: ", config.SentinelEnable)

	rm.isDoingRecovery = true
	if config.SentinelEnable != "yes" {
		rhelper.InitRedis(config.RedisDefaultServer, config.RedisPasswd)
		devserv.ClearOnlineDev()

		go rm.subScribeMsg()
		return
	}

	sentinel, err := NewSentinelClient("tcp", config.SentinelAddrs, nil, 250*time.Millisecond, 250*time.Millisecond, 250*time.Millisecond)
	if err != nil {
		panic(err)
	}
	defer sentinel.Close()

	masterAddr, err := rm.tryToGetMasterAddr(sentinel)
	if !rm.isDoingRecovery {
		log.Println("Discard recovery")
		return
	}

	if err != nil {
		panic(err)
	}

	log.Println("masterAddr", masterAddr)
	rhelper.InitRedis(masterAddr, config.RedisPasswd)
	devserv.ClearOnlineDev()
	go rm.subScribeMsg()
}

func (rm *RecoveryManager) stopRecovery() {
	if rm == nil {
		return
	}

	rm.isDoingRecovery = false

	if rm.psc != nil {
		rm.psc.Unsubscribe()
		rm.psc = nil
	}

	rm = nil
}

func doRecovery() {
	if rm != nil {
		rm.stopRecovery()
	}

	rm = &RecoveryManager{}
	rm.doRecovery()
}
